home *** CD-ROM | disk | FTP | other *** search
/ Visual Cafe 3 / Visual Cafe 3.ISO / Vcafe / JFC.bin / WrappedPlainView.java < prev   
Text File  |  1998-06-30  |  22KB  |  639 lines

  1. /*
  2.  * @(#)WrappedPlainView.java    1.12 98/04/09
  3.  * 
  4.  * Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
  5.  * 
  6.  * This software is the confidential and proprietary information of Sun
  7.  * Microsystems, Inc. ("Confidential Information").  You shall not
  8.  * disclose such Confidential Information and shall use it only in
  9.  * accordance with the terms of the license agreement you entered into
  10.  * with Sun.
  11.  * 
  12.  * SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
  13.  * SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  14.  * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
  15.  * PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
  16.  * SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
  17.  * THIS SOFTWARE OR ITS DERIVATIVES.
  18.  * 
  19.  */
  20. package com.sun.java.swing.text;
  21.  
  22. import java.util.Vector;
  23. import java.util.Properties;
  24. import java.awt.*;
  25. import com.sun.java.swing.event.*;
  26.  
  27. /**
  28.  * View of plain text (text with only one font and color)
  29.  * that does line-wrapping.  This view expects that its
  30.  * associated element has child elements that represent
  31.  * the lines it should be wrapping.  It is implemented
  32.  * as a vertical box that contains logical line views.
  33.  * The logical line views are nested classes that render
  34.  * the logical line as multiple physical line if the logical
  35.  * line is too wide to fit within the allocation.  The
  36.  * line views draw upon the outer class for its state
  37.  * to reduce their memory requirements.
  38.  * <p>
  39.  * The line views do all of their rendering through the
  40.  * <code>drawLine</code> method which in turn does all of
  41.  * its rendering through the <code>drawSelectedText</code>
  42.  * and <code>drawUnselectedText</code> methods.  This 
  43.  * enables subclasses to easily specialize the rendering
  44.  * without concern for the layout aspects.
  45.  *
  46.  * @author  Timothy Prinzing
  47.  * @version 1.12 04/09/98
  48.  * @see     View
  49.  */
  50. public class WrappedPlainView extends BoxView implements TabExpander {
  51.  
  52.     /**
  53.      * Creates a new WrappedPlainView.  Lines will be wrapped
  54.      * on character boundries.
  55.      *
  56.      * @param elem the element underlying the view
  57.      */
  58.     public WrappedPlainView(Element elem) {
  59.     this(elem, false);
  60.     }
  61.  
  62.     /**
  63.      * Creates a new WrappedPlainView.  Lines can be wrapped on
  64.      * either character or word boundries depending upon the
  65.      * setting of the wordWrap parameter.
  66.      *
  67.      * @param elem the element underlying the view
  68.      * @param wordWrap should lines be wrapped on word boundries?
  69.      */
  70.     public WrappedPlainView(Element elem, boolean wordWrap) {
  71.     super(elem, Y_AXIS);
  72.     lineBuffer = new Segment();
  73.     this.wordWrap = wordWrap;
  74.     }
  75.  
  76.     /**
  77.      * Returns the tab size set for the document, defaulting to 8.
  78.      *
  79.      * @return the tab size
  80.      */
  81.     protected int getTabSize() {
  82.         Integer i = (Integer) getDocument().getProperty(PlainDocument.tabSizeAttribute);
  83.         int size = (i != null) ? i.intValue() : 8;
  84.         return size;
  85.     }
  86.  
  87.     /**
  88.      * Renders a line of text, suppressing whitespace at the end
  89.      * and expanding any tabs.  This is implemented to make calls
  90.      * to the methods <code>drawUnselectedText</code> and 
  91.      * <code>drawSelectedText</code> so that the way selected and 
  92.      * unselected text are rendered can be customized.
  93.      *
  94.      * @param p0 the starting document location to use >= 0
  95.      * @param p1 the ending document location to use >= p1
  96.      * @param g the graphics context
  97.      * @param x the starting X position >= 0
  98.      * @param y the starting Y position >= 0
  99.      * @see #drawUnselectedText
  100.      * @see #drawSelectedText
  101.      */
  102.     protected void drawLine(int p0, int p1, Graphics g, int x, int y) {
  103.         try {
  104.             p1 = Math.min(getDocument().getLength(), p1);
  105.             if (sel0 == sel1) {
  106.                 // no selection
  107.                 drawUnselectedText(g, x, y, p0, p1);
  108.             } else if ((p0 >= sel0 && p0 <= sel1) && (p1 >= sel0 && p1 <= sel1)) {
  109.                 drawSelectedText(g, x, y, p0, p1);
  110.             } else if (sel0 >= p0 && sel0 <= p1) {
  111.                 if (sel1 >= p0 && sel1 <= p1) {
  112.                     x = drawUnselectedText(g, x, y, p0, sel0);
  113.                     x = drawSelectedText(g, x, y, sel0, sel1);
  114.                     drawUnselectedText(g, x, y, sel1, p1);
  115.                 } else {
  116.                     x = drawUnselectedText(g, x, y, p0, sel0);
  117.                     drawSelectedText(g, x, y, sel0, p1);
  118.                 }
  119.             } else if (sel1 >= p0 && sel1 <= p1) {
  120.                 x = drawSelectedText(g, x, y, p0, sel1);
  121.                 drawUnselectedText(g, x, y, sel1, p1);
  122.             } else {
  123.                 drawUnselectedText(g, x, y, p0, p1);
  124.             }
  125.         } catch (BadLocationException e) {
  126.             throw new StateInvariantError("Can't render: " + p0 + "," + p1);
  127.         }
  128.     }
  129.  
  130.     /**
  131.      * Renders the given range in the model as normal unselected
  132.      * text.  
  133.      *
  134.      * @param g the graphics context
  135.      * @param x the starting X coordinate >= 0
  136.      * @param y the starting Y coordinate >= 0
  137.      * @param p0 the beginning position in the model >= 0
  138.      * @param p1 the ending position in the model >= p0
  139.      * @returns the X location of the end of the range >= 0
  140.      * @exception BadLocationException if the range is invalid
  141.      */
  142.     protected int drawUnselectedText(Graphics g, int x, int y, 
  143.                                      int p0, int p1) throws BadLocationException {
  144.         g.setColor(unselected);
  145.         Document doc = getDocument();
  146.         doc.getText(p0, p1 - p0, lineBuffer);
  147.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  148.     }
  149.  
  150.     /**
  151.      * Renders the given range in the model as selected text.  This
  152.      * is implemented to render the text in the color specified in
  153.      * the hosting component.  It assumes the highlighter will render
  154.      * the selected background.
  155.      *
  156.      * @param g the graphics context
  157.      * @param x the starting X coordinate >= 0
  158.      * @param y the starting Y coordinate >= 0
  159.      * @param p0 the beginning position in the model >= 0
  160.      * @param p1 the ending position in the model >= p0
  161.      * @returns the location of the end of the range.
  162.      * @exception BadLocationException if the range is invalid
  163.      */
  164.     protected int drawSelectedText(Graphics g, int x, 
  165.                                    int y, int p0, int p1) throws BadLocationException {
  166.         g.setColor(selected);
  167.         Document doc = getDocument();
  168.         doc.getText(p0, p1 - p0, lineBuffer);
  169.         return Utilities.drawTabbedText(lineBuffer, x, y, g, this, p0);
  170.     }
  171.  
  172.     /**
  173.      * Gives access to a buffer that can be used to fetch 
  174.      * text from the associated document.
  175.      *
  176.      * @returns the buffer
  177.      */
  178.     protected final Segment getLineBuffer() {
  179.         return lineBuffer;
  180.     }
  181.  
  182.     /**
  183.      * This is called by the nested wrapped line
  184.      * views to determine the break location.  This can
  185.      * be reimplemented to alter the breaking behavior.
  186.      * It will either break at word or character boundries
  187.      * depending upon the break argument given at
  188.      * construction.
  189.      */
  190.     protected int calculateBreakPosition(int p0, int p1) {
  191.     int p;
  192.     loadText(p0, p1);
  193.     if (wordWrap) {
  194.         p = p0 + Utilities.getBreakLocation(lineBuffer, metrics,
  195.                         tabBase, tabBase + getWidth(),
  196.                         this, p0);
  197.     } else {
  198.         p = p0 + Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  199.                            tabBase, tabBase + getWidth(),
  200.                            this, p0);
  201.     }
  202.     return p;
  203.     }
  204.  
  205.     /**
  206.      * Loads all of the children to initialize the view.
  207.      * This is called by the <code>setParent</code> method.
  208.      * Subclasses can reimplement this to initialize their
  209.      * child views in a different manner.  The default
  210.      * implementation creates a child view for each 
  211.      * child element.
  212.      *
  213.      * @param f the view factory
  214.      */
  215.     protected void loadChildren(ViewFactory f) {
  216.         Element e = getElement();
  217.         int n = e.getElementCount();
  218.         if (n > 0) {
  219.             View[] added = new View[n];
  220.             for (int i = 0; i < n; i++) {
  221.                 added[i] = new WrappedLine(e.getElement(i));
  222.             }
  223.             replace(0, 0, added);
  224.         }
  225.     }
  226.  
  227.     /**
  228.      * Update the child views in response to a 
  229.      * document event.
  230.      */
  231.     void updateChildren(DocumentEvent e, Shape a) {
  232.         Element elem = getElement();
  233.         DocumentEvent.ElementChange ec = e.getChange(elem);
  234.         if (ec != null) {
  235.             // the structure of this element changed.
  236.             Element[] removedElems = ec.getChildrenRemoved();
  237.             Element[] addedElems = ec.getChildrenAdded();
  238.             View[] added = new View[addedElems.length];
  239.             for (int i = 0; i < addedElems.length; i++) {
  240.                 added[i] = new WrappedLine(addedElems[i]);
  241.             }
  242.             replace(ec.getIndex(), removedElems.length, added);
  243.  
  244.             // should damge a little more intelligently.
  245.             if (a != null) {
  246.                 preferenceChanged(null, true, true);
  247.                 getContainer().repaint();
  248.             }
  249.         }
  250.  
  251.     // update font metrics which may be used by the child views
  252.     updateMetrics();
  253.     }
  254.  
  255.     /**
  256.      * Load the text buffer with the given range
  257.      * of text.  This is used by the fragments 
  258.      * broken off of this view as well as this 
  259.      * view itself.
  260.      */
  261.     final void loadText(int p0, int p1) {
  262.     try {
  263.         Document doc = getDocument();
  264.         doc.getText(p0, p1 - p0, lineBuffer);
  265.     } catch (BadLocationException bl) {
  266.         throw new StateInvariantError("Can't get line text");
  267.     }
  268.     }
  269.  
  270.     final void updateMetrics() {
  271.     Component host = getContainer();
  272.     Font f = host.getFont();
  273.     metrics = host.getFontMetrics(f);
  274.     tabSize = getTabSize() * metrics.charWidth('m');
  275.     }
  276.  
  277.     // --- TabExpander methods ------------------------------------------
  278.  
  279.     /**
  280.      * Returns the next tab stop position after a given reference position.
  281.      * This implementation does not support things like centering so it
  282.      * ignores the tabOffset argument.
  283.      *
  284.      * @param x the current position >= 0
  285.      * @param tabOffset the position within the text stream
  286.      *   that the tab occurred at >= 0.
  287.      * @return the tab stop, measured in points >= 0
  288.      */
  289.     public float nextTabStop(float x, int tabOffset) {
  290.         int ntabs = ((int) x - tabBase) / tabSize;
  291.         return tabBase + ((ntabs + 1) * tabSize);
  292.     }
  293.  
  294.     
  295.     // --- View methods -------------------------------------
  296.  
  297.     /**
  298.      * Renders using the given rendering surface and area 
  299.      * on that surface.  This is implemented to stash the
  300.      * selection positions, selection colors, and font
  301.      * metrics for the nested lines to use.
  302.      *
  303.      * @param g the rendering surface to use
  304.      * @param a the allocated region to render into
  305.      *
  306.      * @see View#paint
  307.      */
  308.     public void paint(Graphics g, Shape a) {
  309.     Rectangle alloc = (Rectangle) a;
  310.     tabBase = alloc.x;
  311.     JTextComponent host = (JTextComponent) getContainer();
  312.     sel0 = host.getSelectionStart();
  313.     sel1 = host.getSelectionEnd();
  314.     unselected = (host.isEnabled()) ? 
  315.         host.getForeground() : host.getDisabledTextColor();
  316.     Caret c = host.getCaret();
  317.         selected = c.isSelectionVisible() ? host.getSelectedTextColor() : unselected;
  318.     g.setFont(host.getFont());
  319.  
  320.         // superclass paints the children
  321.         super.paint(g, a);
  322.     }
  323.  
  324.     /**
  325.      * Sets the size of the view.  If the size has changed, layout
  326.      * is redone.  The size is the full size of the view including
  327.      * the inset areas.  
  328.      *
  329.      * @param width the width >= 0
  330.      * @param height the height >= 0
  331.      */
  332.     public void setSize(float width, float height) {
  333.     updateMetrics();
  334.     if ((int) width != getWidth()) {
  335.         // invalidate the view itself since the childrens
  336.         // desired widths will be based upon this views width.
  337.         preferenceChanged(null, true, true);
  338.         widthChanging = true;
  339.     }
  340.     super.setSize(width, height);
  341.     widthChanging = false;
  342.     }
  343.  
  344.     /**
  345.      * Determines the preferred span for this view along an
  346.      * axis.  This is implemented to provide the superclass
  347.      * behavior after first making sure that the current font
  348.      * metrics are cached (for the nested lines which use
  349.      * the metrics to determine the height of the potentially
  350.      * wrapped lines).
  351.      *
  352.      * @param axis may be either View.X_AXIS or View.Y_AXIS
  353.      * @returns  the span the view would like to be rendered into.
  354.      *           Typically the view is told to render into the span
  355.      *           that is returned, although there is no guarantee.  
  356.      *           The parent may choose to resize or break the view.
  357.      * @see View#getPreferredSpan
  358.      */
  359.     public float getPreferredSpan(int axis) {
  360.     updateMetrics();
  361.     return super.getPreferredSpan(axis);
  362.     }
  363.  
  364.     /**
  365.      * Gives notification that something was inserted into the 
  366.      * document in a location that this view is responsible for.
  367.      * This is implemented to simply update the children.
  368.      *
  369.      * @param e the change information from the associated document
  370.      * @param a the current allocation of the view
  371.      * @param f the factory to use to rebuild if the view has children
  372.      * @see View#insertUpdate
  373.      */
  374.     public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  375.         updateChildren(e, a);
  376.  
  377.         Rectangle alloc = ((a != null) && isAllocationValid()) ? 
  378.             getInsideAllocation(a) : null;
  379.         int pos = e.getOffset();
  380.         View v = getViewAtPosition(pos, alloc);
  381.         if (v != null) {
  382.             v.insertUpdate(e, alloc, f);
  383.         }
  384.     }
  385.  
  386.     /**
  387.      * Gives notification that something was removed from the 
  388.      * document in a location that this view is responsible for.
  389.      * This is implemented to simply update the children.
  390.      *
  391.      * @param e the change information from the associated document
  392.      * @param a the current allocation of the view
  393.      * @param f the factory to use to rebuild if the view has children
  394.      * @see View#removeUpdate
  395.      */
  396.     public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  397.         updateChildren(e, a);
  398.  
  399.         Rectangle alloc = ((a != null) && isAllocationValid()) ? 
  400.             getInsideAllocation(a) : null;
  401.         int pos = e.getOffset();
  402.         View v = getViewAtPosition(pos, alloc);
  403.         if (v != null) {
  404.             v.removeUpdate(e, alloc, f);
  405.         }
  406.     }
  407.  
  408.     /**
  409.      * Gives notification from the document that attributes were changed
  410.      * in a location that this view is responsible for.
  411.      *
  412.      * @param e the change information from the associated document
  413.      * @param a the current allocation of the view
  414.      * @param f the factory to use to rebuild if the view has children
  415.      * @see View#changedUpdate
  416.      */
  417.     public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  418.         updateChildren(e, a);
  419.     }
  420.  
  421.     // --- variables -------------------------------------------
  422.  
  423.     FontMetrics metrics;
  424.     Segment lineBuffer;
  425.     boolean widthChanging;
  426.     int tabBase;
  427.     int tabSize;
  428.     boolean wordWrap;
  429.     
  430.     int sel0;
  431.     int sel1;
  432.     Color unselected;
  433.     Color selected;
  434.  
  435.  
  436.     /**
  437.      * Simple view of a line that wraps if it doesn't
  438.      * fit withing the horizontal space allocated.
  439.      * This class tries to be lightweight by carrying little 
  440.      * state of it's own and sharing the state of the outer class 
  441.      * with it's sibblings.
  442.      */
  443.     class WrappedLine extends View {
  444.  
  445.         WrappedLine(Element elem) {
  446.             super(elem);
  447.         }
  448.  
  449.         /**
  450.          * Calculate the number of lines that will be rendered
  451.          * by logical line when it is wrapped.
  452.          */
  453.         final int calculateLineCount() {
  454.             int nlines = 0;
  455.             int p1 = getEndOffset();
  456.             for (int p0 = getStartOffset(); p0 < p1; ) {
  457.                 nlines += 1;
  458.         int p = calculateBreakPosition(p0, p1);
  459.                 p0 = (p == p0) ? p1 : p;
  460.             }
  461.             return nlines;
  462.         }
  463.  
  464.         /**
  465.          * Determines the preferred span for this view along an
  466.          * axis.
  467.          *
  468.          * @param axis may be either X_AXIS or Y_AXIS
  469.          * @returns  the span the view would like to be rendered into.
  470.          *           Typically the view is told to render into the span
  471.          *           that is returned, although there is no guarantee.  
  472.          *           The parent may choose to resize or break the view.
  473.          * @see View#getPreferredSpan
  474.          */
  475.         public float getPreferredSpan(int axis) {
  476.             switch (axis) {
  477.             case View.X_AXIS:
  478.                 return getWidth();
  479.             case View.Y_AXIS:
  480.         if (nlines == 0 || widthChanging) {
  481.             nlines = calculateLineCount();
  482.         }
  483.                 int h = nlines * metrics.getHeight();
  484.                 return h;
  485.             default:
  486.                 throw new IllegalArgumentException("Invalid axis: " + axis);
  487.             }
  488.         }
  489.  
  490.         /**
  491.          * Renders using the given rendering surface and area on that
  492.          * surface.  The view may need to do layout and create child
  493.          * views to enable itself to render into the given allocation.
  494.          *
  495.          * @param g the rendering surface to use
  496.          * @param a the allocated region to render into
  497.          * @see View#paint
  498.          */
  499.         public void paint(Graphics g, Shape a) {
  500.             Rectangle alloc = (Rectangle) a;
  501.             int y = alloc.y + metrics.getAscent();
  502.             int x = alloc.x;
  503.  
  504.             int p1 = getEndOffset();
  505.             for (int p0 = getStartOffset(); p0 < p1; ) {
  506.         int p = calculateBreakPosition(p0, p1);
  507.                 drawLine(p0, p, g, x, y);
  508.                 
  509.                 p0 = (p == p0) ? p1 : p;
  510.                 y += metrics.getHeight();
  511.             }
  512.         }
  513.  
  514.         /**
  515.          * Provides a mapping from the document model coordinate space
  516.          * to the coordinate space of the view mapped to it.
  517.          *
  518.          * @param pos the position to convert
  519.          * @param a the allocated region to render into
  520.          * @return the bounding box of the given position is returned
  521.          * @exception BadLocationException  if the given position does not represent a
  522.          *   valid location in the associated document
  523.          * @see View#modelToView
  524.          */
  525.         public Shape modelToView(int pos, Shape a) throws BadLocationException {
  526.             Rectangle alloc = (Rectangle) a;
  527.             alloc.height = metrics.getHeight();
  528.             alloc.width = 1;
  529.             
  530.             int p1 = getEndOffset();
  531.             for (int p0 = getStartOffset(); p0 < p1; ) {
  532.         int p = calculateBreakPosition(p0, p1);
  533.                 if ((pos >= p0) && (pos < p)) {
  534.                     // it's in this line
  535.                     loadText(p0, pos);
  536.                     alloc.x += Utilities.getTabbedTextWidth(lineBuffer, metrics, 
  537.                                                             alloc.x, 
  538.                                                             WrappedPlainView.this, p0);
  539.                     return alloc;
  540.                 }
  541.                 
  542.                 p0 = (p == p0) ? p1 : p;
  543.                 alloc.y += alloc.height;
  544.             }
  545.             throw new BadLocationException(null, pos);
  546.         }
  547.  
  548.         /**
  549.          * Provides a mapping from the view coordinate space to the logical
  550.          * coordinate space of the model.
  551.          *
  552.          * @param x the X coordinate
  553.          * @param y the Y coordinate
  554.          * @param a the allocated region to render into
  555.          * @return the location within the model that best represents the
  556.          *  given point in the view
  557.          * @see View#viewToModel
  558.          */
  559.         public int viewToModel(float fx, float fy, Shape a) {
  560.         Rectangle alloc = (Rectangle) a;
  561.         Document doc = getDocument();
  562.         int x = (int) fx;
  563.         int y = (int) fy;
  564.         if (y < alloc.y) {
  565.         // above the area covered by this icon, so the the position
  566.         // is assumed to be the start of the coverage for this view.
  567.         return getStartOffset();
  568.         } else if (y > alloc.y + alloc.height) {
  569.         // below the area covered by this icon, so the the position
  570.         // is assumed to be the end of the coverage for this view.
  571.         return getEndOffset() - 1;
  572.         } else {
  573.         // positioned within the coverage of this view vertically,
  574.         // so we figure out which line the point corresponds to.
  575.         // if the line is greater than the number of lines contained, then
  576.         // simply use the last line as it represents the last possible place
  577.         // we can position to.
  578.         alloc.height = metrics.getHeight();
  579.         int p1 = getEndOffset();
  580.         for (int p0 = getStartOffset(); p0 < p1; ) {
  581.             int p = calculateBreakPosition(p0, p1);
  582.             if ((y >= alloc.y) && (y < (alloc.y + alloc.height))) {
  583.             // it's in this line
  584.             if (x < alloc.x) {
  585.                 // point is to the left of the line
  586.                 return p0;
  587.             } else if (x > alloc.x + alloc.width) {
  588.                 // point is to the right of the line
  589.                 return p;
  590.             } else {
  591.                 // Determine the offset into the text
  592.                 int n = Utilities.getTabbedTextOffset(lineBuffer, metrics, 
  593.                                     alloc.x, x, 
  594.                                     WrappedPlainView.this, p0);
  595.                 return Math.min(p0 + n, p1 - 1);
  596.             }
  597.             }
  598.             
  599.             p0 = (p == p0) ? p1 : p;
  600.             alloc.y += alloc.height;
  601.         }
  602.         return getEndOffset() - 1;
  603.         }
  604.     }
  605.  
  606.         public void insertUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  607.         int n = calculateLineCount();
  608.         if (this.nlines != n) {
  609.         this.nlines = n;
  610.         WrappedPlainView.this.preferenceChanged(this, false, true);
  611.         }
  612.         if (a != null) {
  613.                 Component c = getContainer();
  614.                 Rectangle alloc = (Rectangle) a;
  615.                 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  616.             }
  617.         }
  618.  
  619.         public void removeUpdate(DocumentEvent e, Shape a, ViewFactory f) {
  620.         int n = calculateLineCount();
  621.         if (this.nlines != n) {
  622.         this.nlines = n;
  623.         WrappedPlainView.this.preferenceChanged(this, false, true);
  624.         }
  625.         if (a != null) {
  626.                 Component c = getContainer();
  627.                 Rectangle alloc = (Rectangle) a;
  628.                 c.repaint(alloc.x, alloc.y, alloc.width, alloc.height);
  629.             }
  630.         }
  631.  
  632.         // --- variables ---------------------------------------
  633.  
  634.         int nlines;
  635.     }
  636.     
  637. }
  638.  
  639.